Lab 03 - introduction to SFML

Introduction to SFML

Use of external libraries

Until now we used standard libraries provided with a set of development tools (SDK) for a given system. Using the library required only attaching an appropriate header file in the program code. Due to the fact that these were standard libraries, their location was known to the compiler.

Adding an external library requires modification of the project settings by indicating where the library is located. In addition, the SFML library that we attach will be provided in compiled form. This means that the header file will contain only function declarations, and their implementation is delivered in the form of files containing compiled code, which should be indicated to the linker in order to correctly build the project.

Creating a project that uses SFML

Create a plain C++ application project. Place the content into main.cpp file with:

#include <SFML/Window.hpp>
#include <SFML/Graphics.hpp>

int main() {
    sf::RenderWindow window(sf::VideoMode(sf::Vector2u(800, 600)), "My window");

    sf::CircleShape circle(100.0f);
    circle.setPosition(sf::Vector2f(100.0f, 300.0f));
    circle.setFillColor(sf::Color(100, 250, 50));

    sf::RectangleShape rectangle(sf::Vector2f(120.0f, 60.0f));
    rectangle.setPosition(sf::Vector2f(500.0f, 400.0f));
    rectangle.setFillColor(sf::Color(100, 50, 250));

    sf::ConvexShape triangle;
    triangle.setPointCount(3);
    triangle.setPoint(0, sf::Vector2f(0.0f, 0.0f));
    triangle.setPoint(1, sf::Vector2f(0.0f, 100.0f));
    triangle.setPoint(2, sf::Vector2f(140.0f, 40.0f));
    triangle.setOutlineColor(sf::Color::Red);
    triangle.setOutlineThickness(5);
    triangle.setPosition(sf::Vector2f(600.0f, 100.0f));

    while (window.isOpen()) {
        while (auto event = window.pollEvent()) {
            if (event->is<sf::Event::Closed>())
                window.close();
        }

        window.clear(sf::Color::Black);

        window.draw(circle);
        window.draw(rectangle);
        window.draw(triangle);

        window.display();
    }

    return 0;
}

Try to compile the project. The compiler will return an error if the header file SFML/Window.h is missing from the system paths it searches.

Adding SFML (Windows)

This setup uses precompiled SFML binaries and VS Code configuration. It does not use MSYS2 or terminal commands on Windows.

Download SFML from the official website (https://www.sfml-dev.org/download/), then choose the package that matches your compiler and architecture. In this course we use MinGW 64-bit, so pick the matching GCC MinGW package (not MSVC).

Extract the archive to a fixed path, for example:

C:\libs\SFML-3.0.2

Make sure this folder contains:

After that, modify tasks.json:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "C:/Qt/Tools/mingw1310_64/bin/g++.exe",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "-std=c++17",
                "${workspaceFolder}/*.cpp",
                "-o",
                "${workspaceFolder}/app.exe",
                "-IC:/libs/SFML-3.0.2/include",
                "-LC:/libs/SFML-3.0.2/lib",
                "-lsfml-graphics",
                "-lsfml-window",
                "-lsfml-system"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

Now modify launch.json so VS Code can find SFML .dll files during debug:

{
    "configurations": [
        {
            "name": "C/C++: g++ build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/app.exe",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [
                {
                    "name": "PATH",
                    "value": "C:/libs/SFML-3.0.2/bin;${env:PATH}"
                }
            ],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build active file",
            "miDebuggerPath": "C:/Qt/Tools/mingw1310_64/bin/gdb.exe"
        }
    ],
    "version": "2.0.0"
}

If your MinGW path is different, update both g++.exe and gdb.exe paths accordingly. If your SFML folder name is different, update all C:/libs/SFML-3.0.2/... paths.

After these changes you should be able to build and run SFML code on Windows without MSYS2.

Adding SFML (Linux)

Check if SFML is already installed on your system by running the following command in the terminal:

ls /usr/local/lib | grep sfml

if you see files with sfml in their name, it means that the library is already installed and you can skip the next step and move to modifying task.json and launch.json files. If not, you need to install the library first.

Download SFML from the official repository. To do it, run the following commands in the terminal (ctrl + alt + t to open it and ctrl + shift + v to paste the commands):

sudo apt-get install -y wget build-essential cmake pkg-config libx11-dev libxrandr-dev \
libudev-dev libgl1-mesa-dev libfreetype6-dev libjpeg-dev libopenal-dev \
libflac-dev libvorbis-dev libogg-dev
git clone https://github.com/SFML/SFML.git
cd SFML
git checkout 3.0.x
mkdir build
cd build
cmake .. -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
cmake --build . --config Release --parallel
sudo cmake --install . --prefix=/usr/local
ls /usr/local/lib | grep sfml

After that, modify task.json to include the path to the SFML installation, like this:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: g++ build active file",
            "command": "/usr/bin/g++",
            "args": [
                "-fdiagnostics-color=always",
                "-g",
                "-std=c++17",
                "${file}",
                "-o",
                "${workspaceFolder}/app",
                "-I",
                "/user/local/include",
                "-L",
                "/usr/local/lib",
                "-Wl,-rpath,/usr/local/lib",
                "-lsfml-graphics",
                "-lsfml-window",
                "-lsfml-system"
            ],
            "options": {
                "cwd": "${workspaceFolder}"
            },
            "problemMatcher": [ 
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

Keep in mind that the line "-Wl,-rpath,/home/put/SFML-3.0.2/lib", holds user-specific path, so you should change it to match your system if needed. On PCs in the lab, it should be fine with put user, but on your personal computer it may be different.

Next, modify launch.json to include the path to the SFML library:

{
    "configurations": [
        {
            "name": "C/C++: g++ build and debug active file",
            "type": "cppdbg",
            "request": "launch",
            "program": "${workspaceFolder}/app",
            "args": [],
            "stopAtEntry": false,
            "cwd": "${workspaceFolder}",
            "environment": [
                {
                    "name": "LD_LIBRARY_PATH",
                    "value": "/usr/local/lib:${env:LD_LIBRARY_PATH}"
                }
            ],
            "externalConsole": false,
            "MIMode": "gdb",
            "setupCommands": [
                {
                    "description": "Enable pretty-printing for gdb",
                    "text": "-enable-pretty-printing",
                    "ignoreFailures": true
                },
                {
                    "description": "Set Disassembly Flavor to Intel",
                    "text": "-gdb-set disassembly-flavor intel",
                    "ignoreFailures": true
                }
            ],
            "preLaunchTask": "C/C++: g++ build active file",
            "miDebuggerPath": "/usr/bin/gdb"
        }
    ],
    "version": "2.0.0"
}

After these modifications, you should be able to compile and run the program.

Adding SFML (MacOS)

First install sfml.

brew install sfml

On apple intel it is installed in,

/usr/local/opt/sfml

and on apple silicon in,

/opt/homebrew/opt/sfml

After installation we have to modify task.json. We have to add information about c++ standard -std=c++17 and include and link libraries -I<installation_path>/include, -L<installation_path>/lib, -lsfml-graphics, -lsfml-window, -lsfml-system. So the modified file should look similarly to this:

{
    "tasks": [
        {
            "type": "cppbuild",
            "label": "C/C++: clang build active file",
            "command": "/usr/bin/clang++",
            "args": [
                "-fcolor-diagnostics",
                "-fansi-escape-codes",
                "-std=c++17",
                "-g",
                "${workspaceFolder}/*.cpp",
                "-I/opt/homebrew/opt/sfml/include",
                "-L/opt/homebrew/opt/sfml/lib",
                "-lsfml-graphics",
                "-lsfml-window",
                "-lsfml-system",
                "-o",
                "${fileDirname}/${fileBasenameNoExtension}"
            ],
            "options": {
                "cwd": "${fileDirname}"
            },
            "problemMatcher": [
                "$gcc"
            ],
            "group": {
                "kind": "build",
                "isDefault": true
            },
            "detail": "Task generated by Debugger."
        }
    ],
    "version": "2.0.0"
}

It is not necessary but recommended to add c_cpp_properties.json in .vscode folder. This files tells IntelliSense where to search for libraries and makes autocompleation work. In this new file paste:

{
    "configurations": [
        {
            "name": "Mac",
            "includePath": [
                "${workspaceFolder}/**",
                "/opt/homebrew/opt/sfml/include"
            ],
            "defines": [],
            "compilerPath": "/usr/bin/clang++",
            "cStandard": "c17",
            "cppStandard": "c++17",
            "intelliSenseMode": "macos-clang-arm64"
        }
    ],
    "version": 4
}

Now you can try and compile the code.

Final result

After succesfull compilaiton you should see the rendered image as follows.

SFML library

SFML is a multi-platform library that facilitates the creation of programs that use two-dimensional graphics, e.g. simple games. It contains modules that allow you to generate graphics - drawing simple geometric figures with textures, keyboard/mouse input, sound and network operation.

The library API documentation can be found at the following address: https://www.sfml-dev.org/documentation/3.0.2/

Tutorials describing the basic functionality are available below: https://www.sfml-dev.org/tutorials/3.0/

All library resources are located in the namespace sf. To avoid cluttering the main namespace, we will stop using the using namespace ... directives, and instead precede the corresponding names with sf::, std::, and so on.

The example above shows a typical SFML application runtime. It includes the initialization of the window (class sf::RenderWindow) and the resources used in the program (in this case three shapes), and then performing a cyclical program loop, each course of which leads to the generation of one image frame, until the main window is closed.

Inside the loop, a queue of events (keystrokes, mouse movement, etc.) is checked, and an image frame is generated from the position: the “canvas” of the window is cleared, further objects are drawn, and finally the framebuffer is replaced.


🛠🔥 Assignment 🔥🛠

Analyse how the sample code works. Change the size and layout of the elements on the scene, try to add more. What happens when you change the size of the window after starting the program? In what units are the sizes and position of objects on the scene expressed?


Time measurement, animations

The animation of objects involves displaying slightly modified object in each frame of the image (in each pass of the main loop) in order to get the impression of smooth movement.

In order for the animation of objects to be smooth and its speed independent of the number of frames per second displayed, we have to adjust the subsequent frames of the animation, for example, in one of the following ways:

In today’s example we will use the first method. To measure the time we can use the class sf::Clock. It is a stopwatch clock that starts measuring time when an object is created. It allows you to read the elapsed time and restart measuring. See: sf::Clock documentation.

Basic working example using sf::Clock in game logic:

sf::Clock clock;
while (window.isOpen())
{
    sf::Time elapsed = clock.restart();

    ...
}

sf::Clock::restart method will zero the counter and return the sf::Time object representing time elapsed from the previous restart. See sf::Time documentation. Following methods can be used to extract time in different formats:


🛠🔥 Assignment 🔥🛠

Refer to the documentation for sf::Clock class. Add the sf::Clock object to the program in such a way as to be able to measure the time between successive runs of the main loop. Display the time in the console in microseconds. How many frames per second does your program draw?


Moving an object

All “drawable” objects in the SFML library have a set of methods allowing for their manipulation (transformations) on the screen plane - moving, scaling, rotating. The method to move in relation to the current position is move(sf::Vector2f(float offsetX, float offsetY)).


🛠🔥 Assignment 🔥🛠

Create two variables in the program that represent the speed of one of the objects - the horizontal (x) and vertical (y) components, e.g. rectangle_velocity_x and rectangle_velocity_y. They will denote the speed of the object in pixels per second. Give them values of 50 and 150 respectively.

Use the time calculated in the previous task and in each loop run move rectangular object with the sf::Transformable::move method by the distance that should be covered in the measured time at the set speed. HINT:

distance

Then add the variable rectangle_angular_velocity describing the speed of rotation of the figure around its axis (in degrees per second) and give it a value of 10. Use the rotate(sf::degrees(float degrees)) method to cause the object to rotate.


Simple collisions

Collision detection between objects on stage is the foundation of the mechanics of many games or applications that perform simple physics simulations. Since accurate collision calculations for many irregular objects would be very resource-intensive, some simplifications are used, such as approximating the shape of objects with a rectangle/circle (in a plane) or a cuboid (in 3D space).

The objects we have drawn have the getGlobalBounds() method, which returns the sf::FloatRect rectangle which is the outline of the figure, in the coordinates of the window. Usage example:

sf::FloatRect rectangle_bounds = rectangle.getGlobalBounds();
std::cout << "Top left corner coordinates are: " << rectangle_bounds.position.x << " " << rectangle_bounds.position.y << " " ;
std::cout << "Size od the rectangle is: " << rectangle_bounds.size.x << " " << rectangle_bounds.size.y << std::endl;

🛠🔥 Assignment 🔥🛠

Using the contour of a rotating rectangle, write a set of conditions to verify that it touches one of the edges of the screen. If so, “bounce” it by changing the return speed in the correct direction depending on the wall the object is hitting. You can assume that window has fixed dimensions (or advanced check for current window size using sf::RenderWindow::getSize method).


Colours

Colours in SFML are described in RGB (red, green, blue). Each color component is an 8-bit number without a character (0-255 range). This is a very common way of representing colour in computer graphics. It’s sometimes called RGB888 or RGB 24-bit.

The basic figures in SFML have a contour and fill colour.

For example:

rectangle.setFillColor(sf::Color(255, 255, 0));

will change the colour of the filling of the rectangle to yellow.


🛠🔥 Assignment 🔥🛠

Add a change of colour of the rectangle to a random colour after each bounce, so that the effect is similar to the one shown below:



Authors: Tomasz Mańkowski, Jakub Tomczyński, Dominik Pieczyński